/*
 * multi ir daemon for android -
 * Copyright (C) 2015-2018 AllwinnerTech
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/inotify.h>
#include <sys/poll.h>
#include <linux/input.h>
#include <errno.h>

#include "multiir.h"

struct inputdev_info {
	char devpath[PATH_MAX];
	char name[80];
	char location[80];
	char idstr[80];

	struct input_id id;
	int version;

	int fd;
	int idx;
};

static int print_flags = 0x7;
static struct pollfd ufds[2];
static int nfds = 0;

static const char *target = NULL;
static struct inputdev_info * sourcedev = NULL;

extern int setup_virtual_input_dev(void);
extern int event_forward(struct input_event *event);
extern void report_key_event(void);

static int is_devname_match(const char *devpath, const char *tname)
{
	int fd;
	char name[80];

	if (!devpath || !tname)
		return 0;

	fd = open(devpath, O_RDWR);
	if (fd<0) {
		multiir_error("could not open '%s', %s",
			devpath, strerror(errno));
		return 0;
	}
	name[sizeof(name)-1] = '\0';
	if(ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1) {
		multiir_error("could not get device name for '%s', %s",
			devpath, strerror(errno));
		name[0] = '\0';
		goto notmatch;
	}
	if (!strcmp(name, tname)) {
		multiir_debug("find match device '%s'", devpath);
		close(fd);
		return 1;
	}

notmatch:
	close(fd);
	multiir_debug("device not match or ioctl error, %s(%s)", devpath, name);
	return 0;
}
static int try_to_find_device(const char *devname, char *path)
{
	const char *dirname = "/dev/input";
    char devpath[PATH_MAX];
    char *p;
    DIR *dir;
    struct dirent *de;

	if (!devname || !path)
		return -1;

	path[0] = '\0';
    dir = opendir(dirname);
    if(dir == NULL)
        return -1;
    strcpy(devpath, dirname);
    p = devpath + strlen(devpath);
    *p++ = '/';
    while((de = readdir(dir))) {
        if(de->d_name[0] == '.' &&
           (de->d_name[1] == '\0' ||
            (de->d_name[1] == '.' && de->d_name[2] == '\0')))
            continue;

		strcpy(p, de->d_name);
		if (is_devname_match(devpath, devname)) {
			strcpy(path, devpath);
			break;
		}
    }
    closedir(dir);
    return 0;
}

static void *open_device(const char *devpath)
{
	struct inputdev_info *info;

	if (!devpath)
		goto errout;

	info = calloc(sizeof(*info), sizeof(char));
	if (!info) {
		multiir_error("could not malloc, %s", strerror(errno));
		goto errout;
	}

	info->fd = open(devpath, O_RDWR);
	if (info->fd < 0) {
		multiir_error("could not open device '%s', %s",
			devpath, strerror(errno));
		free(info);
		goto errout;
	}

	strcpy(info->devpath, devpath);
	if (ioctl(info->fd, EVIOCGVERSION, &info->version)) {
		multiir_warning("could not get driver version for '%s', %s",
			devpath, strerror(errno));
	}
	if (ioctl(info->fd, EVIOCGID, &info->id)) {
		multiir_warning("could not get driver id for '%s', %s",
			devpath, strerror(errno));
    }

	if (ioctl(info->fd, EVIOCGNAME(sizeof(info->name)-1), info->name) < 1) {
		multiir_warning("could not get device name for %s, %s",
			devpath, strerror(errno));
	}
	if (ioctl(info->fd, EVIOCGPHYS(sizeof(info->location)-1), info->location) < 1) {
		multiir_warning("could not get location for %s, %s",
			devpath, strerror(errno));
	}
	if (ioctl(info->fd, EVIOCGUNIQ(sizeof(info->idstr) - 1), info->idstr) < 1) {
		multiir_warning("could not get idstring for %s, %s",
			devpath, strerror(errno));
    }

	if (print_flags & PRINT_DEBUG) {
		fprintf(stdout, "    devpath:  %s\n", info->devpath);
		fprintf(stdout, "    bus:      %04x\n"
					 	"    vendor    %04x\n"
						"    product   %04x\n"
						"    version   %04x\n",
						info->id.bustype, info->id.vendor,
						info->id.product, info->id.version);
		fprintf(stdout, "    name:     \"%s\"\n", info->name);
		fprintf(stdout, "    location: \"%s\"\n"
						"    id:       \"%s\"\n", info->location, info->idstr);
		fprintf(stdout, "    version:  %d.%d.%d\n",
						info->version >> 16,
						(info->version >> 8) & 0xff,
						info->version & 0xff);
	}

	ufds[1].fd = info->fd;
	ufds[1].events = POLLIN;
	nfds++;
	multiir_debug("add device '%s' to poll", devpath);	
	return info;
errout:
	multiir_error("could open device '%s'!", devpath);
	return NULL;
}

void close_device(const char *devpath)
{
	if (!sourcedev)
		return;

	if (!strcmp(sourcedev->devpath, devpath)) {
		close(sourcedev->fd);
		free(sourcedev);
		sourcedev = NULL;

		nfds--;
		ufds[nfds].fd = -1;
		ufds[nfds].events = 0;
		multiir_debug("close device '%s'", devpath);	
	} else {
		multiir_debug("device '%s' not open, ignore", devpath);
	}
}

static int notify_process(const char *dirname, int nfd)
{
	int res;
	char devpath[PATH_MAX];
	char *p;
	char event_buf[512];
	int event_size;
	int event_pos = 0;
	struct inotify_event *event;

	res = read(nfd, event_buf, sizeof(event_buf));
	if (res < (int)sizeof(*event)) {
		if (errno == EINTR)
			return 0;
		multiir_error("could not get event, %s", strerror(errno));
		return 1;
	}
	multiir_debug("got %d bytes of event information", res);

	strcpy(devpath, dirname);
	p = devpath + strlen(devpath);
	*p++ = '/';

	while(res >= (int)sizeof(*event)) {
		event = (struct inotify_event *)(event_buf + event_pos);
		multiir_debug("%d: %08x \"%s\"",
			event->wd, event->mask, event->len ? event->name : "");

		if (event->len) {
			strcpy(p, event->name);
			if (event->mask & IN_CREATE) {
				if (!sourcedev && is_devname_match(devpath, target)) {
					multiir_debug("find target device, path=%s", devpath);
					sourcedev = open_device(devpath);
				}
			} else {
				close_device(devpath);
			}
		}

		event_size = sizeof(*event) + event->len;
		res -= event_size;
		event_pos += event_size;
	}

	return 0;
}

void input_event_process(int nfd)
{
	int res;
	struct input_event event;

	res = read(nfd, &event, sizeof(event));
	if (res < (int)sizeof(event)) {
		multiir_error("could not get event");
		return;
	}

	multiir_debug("%04x %04x %08x", event.type, event.code, event.value);
	event_forward(&event);
}

static void usage(char *name)
{
    fprintf(stderr, "Usage: %s [-n name] [-v [mask]] [-h]\n", name);
    fprintf(stderr, "    -n: the input device's name to monitor\n");
    fprintf(stderr, "    -v: verbosity mask (errs=1, warning=2, debug=3)\n");
    fprintf(stderr, "    -h: print this message\n");
}

int main(int argc, char *argv[])
{
	int err, c;
	const char *devname = NULL;
	const char *inotify_path = "/dev/input";
	char devpath[PATH_MAX];

	do {
		c = getopt(argc, argv, "n:v::h");
		if (c == EOF)
			break;
		switch (c) {
		case 'n':
			devname = optarg;
			break;
		case 'v':
			if (optarg)
				print_flags = strtoul(optarg, NULL, 0);
			else
				print_flags = 0xff;
			break;
		case 'h':
		default:
			usage(argv[0]);
			exit(1);
		}
	} while(1);

	if (optind != argc) {
		usage(argv[0]);
		exit(1);
	}

//	setup_virtual_input_dev();
	target = devname ? devname : "sunxi-ir";
	memset(ufds, 0, sizeof(ufds));
	memset(&sourcedev, 0, sizeof(sourcedev));

	/* inotify when '/dev/inpu't dir change */
	nfds = 1;
	ufds[0].fd = inotify_init();
	ufds[0].events = POLLIN;
	err = inotify_add_watch(ufds[0].fd, inotify_path, IN_DELETE | IN_CREATE);
	if (err < 0) {
		multiir_error("could not add watch for '%s', %s",
			inotify_path, strerror(errno));
		return -1;
	}

	/* try to find the target input device */
	err = try_to_find_device(target, devpath);
	if (err==0 && strlen(devpath)) {
		multiir_debug("find target device, path=%s", devpath);
		sourcedev = open_device(devpath);
	}

	while(1) {
		poll(ufds, nfds, -1);
		if (ufds[0].revents & POLLIN) {
			notify_process(inotify_path, ufds[0].fd);
		}
		if (ufds[1].revents & POLLIN) {
			input_event_process(ufds[1].fd);
		}
	}
	return 0;
}

